package com.bolt.support.sqlconfig.builder;

import com.bolt.common.reflect.ClassLoaderUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.common.xml.XNode;
import com.bolt.common.xml.XPathParser;
import com.bolt.convention.exception.ServiceException;
import com.bolt.support.sqlconfig.base.SQLConfiguration;
import com.bolt.support.sqlconfig.base.SqlStatement;

import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public final class XmlSqlConfigBuilder {
    private SQLConfiguration configuration;
    private XPathParser parser;
    private Map<String, XNode> sqlFragments;

    public XmlSqlConfigBuilder() {
    }

    public XmlSqlConfigBuilder(Reader reader, SQLConfiguration configuration) {
        this.parser = new XPathParser(reader, false);
        this.configuration = configuration;
        this.sqlFragments = new HashMap<>();
    }

    public XNode getSqlFragment(String key) {
        return this.sqlFragments.get(key);
    }

    public void parse() {
        configurationSqlElement(parser.evalNode("/sqls"));
    }

    public SqlStatement parseStrXml(String sql) {
        StringReader reader = new StringReader(sql);
        XPathParser parser = new XPathParser(reader, false);
        XNode context = parser.evalNode("/sql");
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(this);
        SqlStatement sqlStatement = statementParser.parseStatementNode(context);
        return sqlStatement;
    }

    private void configurationConvertElement(List<XNode> list) {
        for (XNode convertNode : list) {
            String type = convertNode.getStringAttribute("type");
            String converter = convertNode.getStringAttribute("converter");
            if (StrUtil.isNotBlank(type) || StrUtil.isNotBlank(converter)) {
                Class typeClass = ClassLoaderUtil.loadClass(type);
                Class converterClass = ClassLoaderUtil.loadClass(converter);
                configuration.putCustomConverter(typeClass, converterClass);
            }
        }
    }

    private void configurationSqlElement(XNode context) {
        try {
            globalSqlElement(context.evalNodes("globalsql"));
            configurationConvertElement(context.evalNodes("convert"));
            buildStatementFromContext(context
                    .evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
            throw new ServiceException("Error parsing Mapper XML. Cause: " + e,
                    e);
        }
    }

    private void globalSqlElement(List<XNode> list) {
        for (XNode globalSqlNode : list) {
            String id = globalSqlNode.getStringAttribute("id");
            sqlFragments.put(id, globalSqlNode);
        }
    }

    private void buildStatementFromContext(List<XNode> list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(this);
        for (XNode context : list) {
            SqlStatement sqlStatement = statementParser.parseStatementNode(context);
            configuration.addStatements(sqlStatement);
        }
    }

}
